package handler

import (
	"filestore-server/util"
	redisPool "filestore-server/cache/redis"
	mydb "filestore-server/db"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"log"
	"math"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

// 分块上传
// 接收每一个分块文件进行本地存储，在合并
// 流程 客户端（进行文件分块）=》初始化分块上传信息 =》每一个分块都进行上传 =》所有分块上传完成通知（进行合并文件）

// redis 相关定义
const (
	// 文件路径
	Data_Src = "/data/"
	// 分块文件信息体 key 前缀
	MP_Prefix = "MP_"
	// 分块大小 5M
	ChunkSize  = 5 * 1024 * 1024
	// 分块 redis KEY
    Chunkcount_KEY  = "chunkcount"
    // 文件hush redis KEY
    Filehash_KEY  = "filehash"
	// 文件大小 redis KEY
    Filesize_KEY  = "filesize"
	// 分块信息前缀 redis KEY
    Chkidx_Prefix  = "chkidx_"
)


// 分块信息结构体
type MultipartUploadInfo struct {
	FileHash string // 文件Hash
	FileSize int // 文件大小
	// UploadID 分块唯一标记规则： 用户名加上时间戳
	UploadID string // 上传ID
	ChunkSize int // 分块最后一块大小
	ChunkCount int // 分块总数
}


// initialMultipartUploadHandler 初始化分块上传
func InitialMultipartUploadHandler(w http.ResponseWriter, r *http.Request) {
	// 1.解析参数
	r.ParseForm()
	username := r.Form.Get("username")
	filehash := r.Form.Get("filehash")
	filesize, err := strconv.Atoi(r.Form.Get("filesize"))
	if err != nil {
		w.Write(util.NewRespMsg(-1, "params invalid", nil).JSONBytes())
		return
	}

	// 2.获得redis的一个连接
	redisConn := redisPool.RedisPool().Get()
	defer redisConn.Close()

	// 3.生成分块上传的初始化信息
	upInfo := MultipartUploadInfo{
		FileHash:   filehash,
		FileSize:   filesize,
		UploadID:   username + fmt.Sprintf("%x", time.Now().UnixNano()), // 用户名加上时间戳
		ChunkSize:  ChunkSize, // 5MB
		ChunkCount: int(math.Ceil(float64(filesize) / (ChunkSize))),
	}


	// 4.将初始化信息写入到redis缓存
	// hash 方式存储
	redisConn.Do("HSET", MP_Prefix + upInfo.UploadID, Chunkcount_KEY, upInfo.ChunkCount)
	redisConn.Do("HSET", MP_Prefix + upInfo.UploadID, Filehash_KEY, upInfo.FileHash)
	redisConn.Do("HSET", MP_Prefix + upInfo.UploadID, Filesize_KEY, upInfo.FileSize)


	// 5.成功响应
	w.Write(util.NewRespMsg(0, "OK", upInfo).JSONBytes())
}

// UploadPartHandler 上传文件分块
func UploadPartHandler(w http.ResponseWriter, r *http.Request) {
	// 1.解析用户请求
	//username := r.Form.Get("username")
	uploadID := r.Form.Get("uploadid")
	chunkIndex := r.Form.Get("index")

	// 2.获取redis连接
	redisConn := redisPool.RedisPool().Get()
	defer redisConn.Close()

	// 3.获得文件句柄，用于存储分块内容
	fpath := Data_Src + uploadID + "/" + chunkIndex
	// 0744 权限，当前用户是 7 权限，其他用户全是 4 权限
	// 权限数字含义百度。。
	os.MkdirAll(fpath, 0744)
	fd, err := os.Create(fpath)
	if err != nil {
		w.Write(util.NewRespMsg(-1, "Upload part failed", nil).JSONBytes())
		return
	}
	defer fd.Close()

	// 把这一个分块的文件进行存储
	buf := make([]byte, 1024 * 1024)
	for  {
		n, err := r.Body.Read(buf)
		fd.Write(buf[:n])
		if err != nil {
			break
		}
	}

	// 4.更新redis缓存状态, 标记当前这个分块上传成功
	redisConn.Do("HSET", MP_Prefix + uploadID, Chkidx_Prefix + chunkIndex, 1)

	// 5.成功响应
	w.Write(util.NewRespMsg(0, "OK", nil).JSONBytes())
}

// CompleteUploadHandler 通知上传合并
func CompleteUploadHandler(w http.ResponseWriter, r *http.Request) {
	// 1.解析请求参数
	r.ParseForm()

	uploadid := r.Form.Get("uploadid")
	username := r.Form.Get("username")
	filehash := r.Form.Get("filehash")
	filesize := r.Form.Get("filesize")
	filename := r.Form.Get("filename")

	// 2.获得redis连接
	redisConn := redisPool.RedisPool().Get()
	defer redisConn.Close()

	// 3.通过upload查询redis并判断是否所有分块上传完成
	// redisConn.Do 执行redis命令，然后 redis.Values 返回值
	data, err := redis.Values(redisConn.Do("HGETALL", MP_Prefix + uploadid))
	if err != nil {
		w.Write(util.NewRespMsg(-1, "complete upload failed", nil).JSONBytes())
		return
	}
	// 分块总数
	totalCount := 0
	// 分块完成总数
	chunkCount := 0
	for i := 0; i < len(data); i += 2  {
		k := string(data[i].([]byte))
		v := string(data[i+1].([]byte))
		if k == Chunkcount_KEY {
			totalCount, _ = strconv.Atoi(v)
		} else if strings.HasPrefix(k, Chkidx_Prefix) && v == "1" {
			// 如果完成了，则加 1
			chunkCount ++
		}
	}

	// 只有两个总数一样才算成功
	if totalCount != chunkCount {
		w.Write(util.NewRespMsg(-2, "invaild request", nil).JSONBytes())
		return
	}

	// 4.TODO 合并分块
	// 方式1：Linux系统:  cat `ls | sort -n` > xxx文件
	// 方式2：代码读取这些分块文件，进行合并

	// 输出文件路径
	descPath := SaveFilePath + filename
	// 源文件路径
	srcPath := Data_Src + uploadid
	strCmd := fmt.Sprintf("cd %s && cat `ls | sort -n` > %s", srcPath + uploadid, descPath)
	cmd := exec.Command(strCmd)
	err = cmd.Run()
	if err != nil {
		log.Fatalf("合并分块文件失败！err:%v", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	// 5.更新唯一文件表及使用用户文件表
	fsize, _ := strconv.Atoi(filesize)
	mydb.OnFileUploadFinished(filehash, filename, int64(fsize), descPath)
	mydb.OnUserFileUploadFinished(username, filehash, filename, int64(fsize))

	// 6.响应处理结果
	w.Write(util.NewRespMsg(0, "OK", nil).JSONBytes())
}
